package ch.sv7.tool.dbmanager.db.fix;

import java.io.PrintStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

import ch.sv7.tool.dbmanager.Context;
import ch.sv7.tool.dbmanager.db.dialect.Dialect;
import ch.sv7.tool.dbmanager.db.dialect.DialectException;
import ch.sv7.tool.dbmanager.db.difference.SchemaDifference;
import ch.sv7.tool.dbmanager.db.difference.SchemaDifferences;
import ch.sv7.tool.dbmanager.db.fixbuilder.FilterFixBuilder;
import ch.sv7.tool.dbmanager.db.fixbuilder.FixBuilder;
import ch.sv7.tool.log.Logger;

/**
 * TODO: Document me!
 *
 * @author svt
 *
 */
public class SchemaFixes {

	private static final Logger log = Logger.getLogger(SchemaFixes.class);
	
	private Set fixes = new TreeSet();
	private Dialect dialect;
	private Context context;
	private List fixFilters = new ArrayList();
	
	/**
	 * @param dialect
	 */
	public SchemaFixes(Dialect dialect, Context context, SchemaDifferences differences) {
		super();
		this.dialect = dialect;
		this.context = context;
		manageDifferences(differences);
	}

	private void manageDifferences(SchemaDifferences differences) {


		for (int i=0; i< differences.getSize(); i++) {
			manageDifference(context, differences.getDifference(i));
		}
		Iterator fixIter = fixes.iterator();
	
		while (fixIter.hasNext()) {
			SchemaFix fix = (SchemaFix)fixIter.next();
			fix.setDialect(dialect);
		}
		
		filterFixes();
	}
	
	private void filterFixes() {
		if (fixFilters.size() > 0){
			for (int i=0; i< fixFilters.size(); i++) {
				FilterFixBuilder filter = (FilterFixBuilder)fixFilters.get(i);
				Iterator fixIter = fixes.iterator();
				
				while (fixIter.hasNext()) {
					SchemaFix fix = (SchemaFix)fixIter.next();
					if (filter.ignoreFix(fix)){
						fixIter.remove();
					}
				}				
			}
		}
	}
	
	private void manageDifference(Context context, SchemaDifference diff) {
		SchemaFix[] diffFixes = getFixesForDifference(diff, context, dialect);
		if (diffFixes != null) {
			for (int i=0; i<diffFixes.length; i++) {
				diffFixes[i].addFixedDifference(diff);
			}
		}
		if (diffFixes != null && diffFixes.length > 0) {
			for (int i=0; i< diffFixes.length; i++){
				if (fixes.contains(diffFixes[i])){
					Iterator iter = fixes.iterator();
					while (iter.hasNext()) {
						SchemaFix fix = (SchemaFix)iter.next();
						if (fix.equals(diffFixes[i])){
							fix.addFixedDifferences(diffFixes[i].getFixedDifferences());
						}
					}
				} else {
					fixes.add(diffFixes[i]);
				}
			}
		}
	}
	
	public void dumpSQL(PrintStream out) throws DialectException {
		Iterator iter = fixes.iterator();
		int count = 1;
		out.println("-- " + fixes.size() + " fixes");
		while (iter.hasNext()){
			SchemaFix fix = (SchemaFix)iter.next();
			if (out != null) {
				out.println();
				out.println("-- Fix " + (count) + " ---------------------------------------------");
				dumpFix(fix, out);
				out.println("-- End of Fix " + (count++) + " --------------------------------------");
			}
		}
	}
	
	public void apply(Connection conn, PrintStream out) throws DialectException, SQLException {
		Iterator iter = fixes.iterator();
		conn.setAutoCommit(true);
		int count = 1;
		if (out != null){
			out.println("-- " + fixes.size() + " fixes");
		}
		while (iter.hasNext()){
			SchemaFix fix = (SchemaFix)iter.next();
			fix.executeFixing(conn);
			if (out != null) {
				out.println();
				out.println("-- Fix " + (count) + " ---------------------------------------------");
				dumpFix(fix, out);
				out.println("-- End of Fix " + (count++) + " --------------------------------------");
			}
		}
	}
	
	public static void dumpFix(SchemaFix fix, PrintStream out) throws DialectException {
		if (fix.getFixedDifferences().size() == 1){
			out.print("-- Fix for: ");
			SchemaDifference diff = (SchemaDifference)fix.getFixedDifferences().get(0);
			out.println(diff.getMessage());
		} else {
			out.println("-- Fix for:");
			
			Iterator iter = fix.getFixedDifferences().iterator();
			while (iter.hasNext()){
				SchemaDifference diff = (SchemaDifference)iter.next();
				out.println("--     " + diff.getMessage());
			}			
		}

		out.println(indent("  ", fix.getSQLFix()));		
	}
	
	private static String indent(String prefix, String str) {
		StringBuffer result = new StringBuffer();
		StringTokenizer tokens = new StringTokenizer(str, "\n");
		while (tokens.hasMoreTokens()){
			String token = tokens.nextToken();
			result.append(prefix + token + "\n");
		}
		
		return result.toString();
	}
	
	public int getSize() {
		return fixes.size();
	}
	
	private SchemaFix[] getFixesForDifference(SchemaDifference diff, Context context, Dialect dialect) {
		
		FixBuilder fixBuilder = context.getFixBuilderClass(diff);
		if (fixBuilder != null) {
			if (fixBuilder instanceof FilterFixBuilder){
				fixFilters.add(fixBuilder);
			}
			return fixBuilder.createFixes(diff, dialect, context);
		} else {
			log.info("No fix available for : " + diff.getMessage());
		}
		
		return null;
	}	
}
